package hu.debrecen.adastra.card.service;

import hu.debrecen.adastra.card.repository.domain.Account;
import hu.debrecen.adastra.card.repository.domain.BotAccount;

import java.util.Collection;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.collect.Collections2;
import com.google.common.collect.Maps;

public class GameStoreService {

	private static final Logger LOGGER = LoggerFactory.getLogger(GameStoreService.class);

	private final Map<String, AbstractGame<?>> games = Maps.newConcurrentMap();

	public GameStoreService() {
		super();
	}

	public AbstractGame<?> put(final AbstractGame<?> game) {
		GameStoreService.LOGGER.trace("put game into cache by id({})", game.getGameId());
		this.games.put(game.getGameId(), game);
		return game;
	}

	public AbstractGame<?> getGame(final String gameId) {
		GameStoreService.LOGGER.trace("get game by id({})", gameId);
		Preconditions.checkState(this.games.containsKey(gameId), "Game does not exits.");

		return this.games.get(gameId);
	}

	public Collection<AbstractGame<?>> getAvailableGames(final Account account) {

		GameStoreService.LOGGER.trace("get available games by account({})", account);

		return Collections2.filter(this.getGames(), new Predicate<AbstractGame<?>>() {

			@Override
			public boolean apply(final AbstractGame<?> game) {
				return !game.isFull() && !game.getOwner().equals(account);
			}

		});
	}

	public Collection<AbstractGame<?>> getFinishedGames() {

		return Collections2.filter(this.getGames(), new Predicate<AbstractGame<?>>() {

			@Override
			public boolean apply(final AbstractGame<?> game) {
				return game.isFull() && game.isFinished();
			}

		});

	}

	public Collection<AbstractGame<?>> getRunningGames() {

		return Collections2.filter(this.getGames(), new Predicate<AbstractGame<?>>() {

			@Override
			public boolean apply(final AbstractGame<?> game) {
				return game.isFull() && !game.isFinished();
			}

		});

	}

	public Collection<AbstractGame<?>> getOrphanaGames() {

		return Collections2.filter(this.getGames(), new Predicate<AbstractGame<?>>() {

			@Override
			public boolean apply(final AbstractGame<?> game) {

				// Somebody joined to the game, which was created by Bot and
				// left it;
				if (game.isFull() && (game.getOwner() instanceof BotAccount) && game.isOrphanaGame()) {
					GameStoreService.LOGGER.warn("Game was marked as 'Orphana' because somebody joined to game after left.");
					return true;
				}

				// Somebody created new game and left it;
				if (!game.isFull() && !(game.getOwner() instanceof BotAccount) && game.isOrphanaGame()) {
					GameStoreService.LOGGER.warn("Game was marked as 'Orphana' because somebody created a new game after left.");
					return true;
				}

				// Game was started but players left it.
				if (game.isFull() && !(game.getOwner() instanceof BotAccount) && game.isOrphanaGame()) {
					GameStoreService.LOGGER.warn("Game was marked as 'Orphana' because it was started but players left it.");
					return true;
				}

				return false;
			}

		});

	}

	public Collection<AbstractGame<?>> getExpiredByLastActionGames() {

		return Collections2.filter(this.getRunningGames(), new Predicate<AbstractGame<?>>() {

			@Override
			public boolean apply(final AbstractGame<?> game) {
				return game.isExpiredByLastAction();
			}

		});

	}

	public boolean exists(final String gameId) {
		boolean exists = this.games.containsKey(gameId);
		GameStoreService.LOGGER.trace("exists({}) game by id({})", exists, gameId);
		return exists;
	}

	public AbstractGame<?> getGameByAccount(final Account account) {

		GameStoreService.LOGGER.trace("get game by account({})", account);

		for (AbstractGame<?> game : this.getGames()) {
			if (game.getAccounts().contains(account) && !game.isFinished()) {
				return game;
			}
		}

		throw new IllegalStateException(String.format("There is not any game by account(%s).", account.getUsername()));
	}

	public void removeGame(final AbstractGame<?> game) {
		if (this.exists(game.getGameId())) {
			this.games.remove(game.getGameId());
		}
	}

	private Collection<AbstractGame<?>> getGames() {
		return this.games.values();
	}

	public Collection<AbstractGame<?>> getAvailableGames(final Account account, final String gameOwnerFilter) {

		if (Strings.isNullOrEmpty(gameOwnerFilter)) {
			return this.getAvailableGames(account);
		}

		return Collections2.filter(this.getAvailableGames(account), new Predicate<AbstractGame<?>>() {
			@Override
			public boolean apply(final AbstractGame<?> game) {
				return game.getOwner().getUsername().toLowerCase().startsWith(gameOwnerFilter.toLowerCase());
			}
		});
	}

}
